home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / arm / util / procTools.py < prev    next >
Encoding:
Python Source  |  2012-05-18  |  9.1 KB  |  307 lines

  1. """
  2. Helper functions for querying process and system information from the /proc
  3. contents. Fetching information this way provides huge performance benefits
  4. over lookups via system utilities (ps, netstat, etc). For instance, resolving
  5. connections this way cuts the runtime by around 90% verses the alternatives.
  6. These functions may not work on all platforms (only Linux?).
  7.  
  8. All functions raise IOErrors if unable to read their respective proc files.
  9.  
  10. The method for reading these files (and some of the code) are borrowed from
  11. psutil:
  12. https://code.google.com/p/psutil/
  13. which was written by Jay Loden, Dave Daeschler, Giampaolo Rodola' and is under
  14. the BSD license.
  15. """
  16.  
  17. import os
  18. import sys
  19. import time
  20. import socket
  21. import base64
  22.  
  23. from util import enum, log
  24.  
  25. # cached system values
  26. SYS_START_TIME, SYS_PHYSICAL_MEMORY = None, None
  27. CLOCK_TICKS = os.sysconf(os.sysconf_names["SC_CLK_TCK"])
  28. Stat = enum.Enum("COMMAND", "CPU_UTIME", "CPU_STIME", "START_TIME")
  29.  
  30. CONFIG = {"queries.useProc": True,
  31.           "log.procCallMade": log.DEBUG}
  32.  
  33. def loadConfig(config):
  34.   config.update(CONFIG)
  35.  
  36. def isProcAvailable():
  37.   """
  38.   Provides true if configured to use proc resolution and it's available on the
  39.   platform, false otherwise.
  40.   """
  41.   
  42.   return CONFIG["queries.useProc"] and os.uname()[0] == "Linux"
  43.  
  44. def getSystemStartTime():
  45.   """
  46.   Provides the unix time (seconds since epoch) when the system started.
  47.   """
  48.   
  49.   global SYS_START_TIME
  50.   if not SYS_START_TIME:
  51.     startTime = time.time()
  52.     statFile = open('/proc/stat')
  53.     statLines = statFile.readlines()
  54.     statFile.close()
  55.     
  56.     for line in statLines:
  57.       if line.startswith('btime'):
  58.         SYS_START_TIME = float(line.strip().split()[1])
  59.         break
  60.     
  61.     _logProcRuntime("system start time", "/proc/stat[btime]", startTime)
  62.   
  63.   return SYS_START_TIME
  64.  
  65. def getPhysicalMemory():
  66.   """
  67.   Provides the total physical memory on the system in bytes.
  68.   """
  69.   
  70.   global SYS_PHYSICAL_MEMORY
  71.   if not SYS_PHYSICAL_MEMORY:
  72.     startTime = time.time()
  73.     memFile = open('/proc/meminfo')
  74.     memLines = memFile.readlines()
  75.     memFile.close()
  76.     
  77.     for line in memLines:
  78.       if line.startswith('MemTotal:'):
  79.         SYS_PHYSICAL_MEMORY = int(line.split()[1]) * 1024
  80.     
  81.     _logProcRuntime("system physical memory", "/proc/meminfo[MemTotal]", startTime)
  82.   
  83.   return SYS_PHYSICAL_MEMORY
  84.  
  85. def getPwd(pid):
  86.   """
  87.   Provides the current working directory for the given process.
  88.   
  89.   Arguments:
  90.     pid - queried process
  91.   """
  92.   
  93.   startTime = time.time()
  94.   if pid == 0: cwd = ""
  95.   else: cwd = os.readlink("/proc/%s/cwd" % pid)
  96.   _logProcRuntime("cwd", "/proc/%s/cwd" % pid, startTime)
  97.   return cwd
  98.  
  99. def getUid(pid):
  100.   """
  101.   Provides the user ID the given process is running under. This is None if it
  102.   can't be determined.
  103.   
  104.   Arguments:
  105.     pid - queried process
  106.   """
  107.   
  108.   startTime = time.time()
  109.   statusFile = open("/proc/%s/status" % pid)
  110.   statusFileLines = statusFile.readlines()
  111.   statusFile.close()
  112.   
  113.   result = None
  114.   for line in statusFileLines:
  115.     if line.startswith("Uid:"):
  116.       lineComp = line.split()
  117.       
  118.       if len(lineComp) >= 2 and lineComp[1].isdigit():
  119.         result = lineComp[1]
  120.   
  121.   _logProcRuntime("uid", "/proc/%s/status[Uid]" % pid, startTime)
  122.   return result
  123.  
  124. def getMemoryUsage(pid):
  125.   """
  126.   Provides the memory usage in bytes for the given process of the form:
  127.   (residentSize, virtualSize)
  128.   
  129.   Arguments:
  130.     pid - queried process
  131.   """
  132.   
  133.   # checks if this is the kernel process
  134.   if pid == 0: return (0, 0)
  135.   
  136.   startTime = time.time()
  137.   statusFile = open("/proc/%s/status" % pid)
  138.   statusFileLines = statusFile.readlines()
  139.   statusFile.close()
  140.   
  141.   residentSize, virtualSize = None, None
  142.   for line in statusFileLines:
  143.     if line.startswith("VmRSS"):
  144.       residentSize = int(line.split()[1]) * 1024
  145.       if virtualSize != None: break
  146.     elif line.startswith("VmSize:"):
  147.       virtualSize = int(line.split()[1]) * 1024
  148.       if residentSize != None: break
  149.   
  150.   _logProcRuntime("memory usage", "/proc/%s/status[VmRSS|VmSize]" % pid, startTime)
  151.   return (residentSize, virtualSize)
  152.  
  153. def getStats(pid, *statTypes):
  154.   """
  155.   Provides process specific information. Options are:
  156.   Stat.COMMAND      command name under which the process is running
  157.   Stat.CPU_UTIME    total user time spent on the process
  158.   Stat.CPU_STIME    total system time spent on the process
  159.   Stat.START_TIME   when this process began, in unix time
  160.   
  161.   Arguments:
  162.     pid       - queried process
  163.     statTypes - information to be provided back
  164.   """
  165.   
  166.   startTime = time.time()
  167.   statFilePath = "/proc/%s/stat" % pid
  168.   statFile = open(statFilePath)
  169.   statContents = statFile.read().strip()
  170.   statFile.close()
  171.   
  172.   # contents are of the form:
  173.   # 8438 (tor) S 8407 8438 8407 34818 8438 4202496...
  174.   statComp = []
  175.   cmdStart, cmdEnd = statContents.find("("), statContents.find(")")
  176.   
  177.   if cmdStart != -1 and cmdEnd != -1:
  178.     statComp.append(statContents[:cmdStart])
  179.     statComp.append(statContents[cmdStart + 1:cmdEnd])
  180.     statComp += statContents[cmdEnd + 1:].split()
  181.   
  182.   if len(statComp) != 44:
  183.     raise IOError("stat file had an unexpected format: %s" % statFilePath)
  184.   
  185.   results, queriedStats = [], []
  186.   for statType in statTypes:
  187.     if statType == Stat.COMMAND:
  188.       queriedStats.append("command")
  189.       if pid == 0: results.append("sched")
  190.       else: results.append(statComp[1])
  191.     elif statType == Stat.CPU_UTIME:
  192.       queriedStats.append("utime")
  193.       if pid == 0: results.append("0")
  194.       else: results.append(str(float(statComp[13]) / CLOCK_TICKS))
  195.     elif statType == Stat.CPU_STIME:
  196.       queriedStats.append("stime")
  197.       if pid == 0: results.append("0")
  198.       else: results.append(str(float(statComp[14]) / CLOCK_TICKS))
  199.     elif statType == Stat.START_TIME:
  200.       queriedStats.append("start time")
  201.       if pid == 0: return getSystemStartTime()
  202.       else:
  203.         # According to documentation, starttime is in field 21 and the unit is
  204.         # jiffies (clock ticks). We divide it for clock ticks, then add the
  205.         # uptime to get the seconds since the epoch.
  206.         pStartTime = float(statComp[21]) / CLOCK_TICKS
  207.         results.append(str(pStartTime + getSystemStartTime()))
  208.   
  209.   _logProcRuntime("process %s" % ", ".join(queriedStats), "/proc/%s/stat" % pid, startTime)
  210.   return results
  211.  
  212. def getConnections(pid):
  213.   """
  214.   Provides a listing of connection tuples of the form:
  215.   [(local_ipAddr1, local_port1, foreign_ipAddr1, foreign_port1), ...]
  216.   
  217.   If the information about a connection can't be queried (often due to
  218.   permission issues) then it's excluded from the listing.
  219.   
  220.   Arguments:
  221.     pid - ID of the process to be resolved
  222.   """
  223.   
  224.   if pid == "0": return []
  225.   
  226.   # fetches the inode numbers for socket file descriptors
  227.   startTime = time.time()
  228.   inodes = []
  229.   for fd in os.listdir("/proc/%s/fd" % pid):
  230.     try:
  231.       # File descriptor link, such as 'socket:[30899]'
  232.       fdName = os.readlink("/proc/%s/fd/%s" % (pid, fd))
  233.       
  234.       if fdName.startswith('socket:['):
  235.         inodes.append(fdName[8:-1])
  236.     except OSError:
  237.       pass # most likely couldn't be read due to permissions
  238.   
  239.   if not inodes:
  240.     # unable to fetch any connections for this process
  241.     return []
  242.   
  243.   # check for the connection information from the /proc/net contents
  244.   conn = []
  245.   for procFilePath in ("/proc/net/tcp", "/proc/net/udp"):
  246.     procFile = open(procFilePath)
  247.     procFile.readline() # skip the first line
  248.     
  249.     for line in procFile:
  250.       _, lAddr, fAddr, status, _, _, _, _, _, inode = line.split()[:10]
  251.       
  252.       if inode in inodes:
  253.         # if a tcp connection, skip if it isn't yet established
  254.         if procFilePath.endswith("/tcp") and status != "01":
  255.           continue
  256.         
  257.         localIp, localPort = _decodeProcAddressEncoding(lAddr)
  258.         foreignIp, foreignPort = _decodeProcAddressEncoding(fAddr)
  259.         conn.append((localIp, localPort, foreignIp, foreignPort))
  260.     
  261.     procFile.close()
  262.   
  263.   _logProcRuntime("process connections", "/proc/net/[tcp|udp]", startTime)
  264.   
  265.   return conn
  266.  
  267. def _decodeProcAddressEncoding(addr):
  268.   """
  269.   Translates an address entry in the /proc/net/* contents to a human readable
  270.   form, for instance:
  271.   "0500000A:0016" -> ("10.0.0.5", "22")
  272.   
  273.   Reference:
  274.   http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
  275.   
  276.   Arguments:
  277.     addr - proc address entry to be decoded
  278.   """
  279.   
  280.   ip, port = addr.split(':')
  281.   
  282.   # the port is represented as a two-byte hexadecimal number
  283.   port = str(int(port, 16))
  284.   
  285.   if sys.version_info >= (3,):
  286.     ip = ip.encode('ascii')
  287.   
  288.   # The IPv4 address portion is a little-endian four-byte hexadecimal number.
  289.   # That is, the least significant byte is listed first, so we need to reverse
  290.   # the order of the bytes to convert it to an IP address.
  291.   #
  292.   # This needs to account for the endian ordering as per...
  293.   # http://code.google.com/p/psutil/issues/detail?id=201
  294.   # https://trac.torproject.org/projects/tor/ticket/4777
  295.   
  296.   if sys.byteorder == 'little':
  297.     ip = socket.inet_ntop(socket.AF_INET, base64.b16decode(ip)[::-1])
  298.   else:
  299.     ip = socket.inet_ntop(socket.AF_INET, base64.b16decode(ip))
  300.   
  301.   return (ip, port)
  302.  
  303. def _logProcRuntime(parameter, procLocation, startTime):
  304.   msg = "proc call (%s): %s (runtime: %0.4f)" % (parameter, procLocation, time.time() - startTime)
  305.   log.log(CONFIG["log.procCallMade"], msg)
  306.  
  307.